%% objexprs.erl
%%      processing the abstract syntax tree (AST) of Obj#classname{field1 = Val1, ...} type expressions
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%


%-include_lib("eunit/include/eunit.hrl").
-module(objexprs).
-export([translate_update/7]).

%% Function: translate_update(State1, VarGenFun, Expression, Updates, Definition, CxClass, Line)
%% Purpose: takes as argument an Expression#class{field1 = Val1, ...}-style update expression, and transforms it
%%          to appropriate Erlang expressions (mostly using setelement/3)
%% Arguments:
%%   State1, VarGenFun - The transformation will create new variable names, that have to be distinct.
%%                       These two variables represent an algorithm to do this. They should work as follows:
%%                           {State2, VarName} = VarGenFun(State1)
%%                       Thus, State acts like a counter.
%%   Expression - the object that is updated
%%   Updates - the list of field value assingments, as appeared in the AST of the update-expression
%%   Definition - the definition of the class used in the update (its name appears after the hash-mark (#))
%%   CxClass - The class where the update-expression is placed, or none if the update-expression is from a non-class
%%             module. Affects visibility of fields.
%%   Line - line number to insert into AST nodes
%% Returns:
%%   {State2, TypeCheckExprs, ParallelExprs, CleanExprs} where
%%   State2 is the last state of the variable name generator
%%   TypeCheckExprs is an expression list containing expressions that check the type of Expression
%%   ParallelExprs is an expression list containing expressions that pre-calculate the new field values (like Val1 in the above example)
%%                 Variables bound in one of the expressions of the list are not visible in the others:
%%                 that is why they can be seen as parallels. Post-processing of these expressions is required,
%%                 because they may contain other ECT-syntax subelements.
%%   CleanExprs is an expresion list containing expressions that construct the new updated object.
%%              They do not contain ECT-syntax subelements so there is no need to post-process them.
translate_update(State1, VarGenFun, Expression, Updates1, Definition, CxClass, Line) ->
    {State2, _ObjVarName, ObjVar, PreExprs1} = translate_update_root(State1, VarGenFun, Expression, Definition, Line),
    
    % -substitute field names by {GlobalPos, LocalPos} field references in each update
    Updates2 = resolve_updates(Updates1, Definition, CxClass),
               
    % substitute each expression in Updates to a variable, and collect variable matches in PreExprs

    {State3, Updates3, PreExprs2} = eval_update_exprs(State2, VarGenFun, Updates2, Line),
    
    % -group update commands by their corresponding subobject (GlobalPos)
    % -order groups and updates inside groups to make sure setelement chains will be optimized by compiler
    Updates4 = sort_updates(Updates3),
    
   
    % Create a setelement chain for each sub-object. The innermost element of the setelemet chain is an
    % element call on ObjVar, that gets the original subobject.
    {State4, Updates5} = subobj_updates(State3, VarGenFun, Updates4, ObjVar, Line),

    % the expression that updates the subobjects of the object by a setelement chain
    ReconstructExpr = reconstruct_object(Updates5, ObjVar, Line),

    {State4, 
        PreExprs1, PreExprs2,
        lists:map(fun ({_, {_, UpdExpr}}) -> UpdExpr end, Updates5)++[ReconstructExpr]
    }.

%% Function: translate_update_root(State1, VarGenFun, ObjExpr, Definition, Line)
%% Purpose: Given an update-expression (ObjExpr#class{f1 = V1,...}), creates an AST fragment that
%%          performs a type-check on the object, and if the object is the result of an
%%          expression, then evaluates it into a variable.
%% Arguments:
%%   State1, VarGenFun: used for generating variable names, see head comment of translate_update/7
%%   ObjExpr: the updated object in the update-expression
%%   Definition: definition of the class of the update-expression
%%   Line: line number of the generated AST nodes
%% Returns:
%%   {State2, ObjVarName, ObjVar, PreExprs} where
%%   State2: is the last state of the variable name generator
%%   ObjVarName: is a variable name refering the object that is updated
%%   ObjVar: is a variable refering the object that is updated
%%   PreExprs: list of expressions, that assign the object to ObjVar/ObjVarName (if necessary) and check its type
translate_update_root(State1, _, ObjVar = {var, _, ObjVarName}, Definition, Line) ->
    {State1, ObjVarName, ObjVar, [ast:class_typecheck(Definition, ObjVar, Line)]};
translate_update_root(State1, VarGenFun, Expression, Definition, Line) ->
    {State2, ObjVarName} = VarGenFun(State1),
    ObjVar = ast:var(ObjVarName, Line),
    PreExprs1 = [ast:match(ObjVar , Expression, Line)
                ,ast:class_typecheck(Definition, ObjVar, Line)
                ],
    {State2, ObjVarName, ObjVar, PreExprs1}.

%% Function: sort_updates(Updates2)
%% Purpose:
%%   -group update commands by their corresponding subobject (GlobalPos)
%%   -order groups and updates inside groups to make sure setelement chains will be optimized by compiler
%% Arguments:
%%    Updates2: list of field updates in the form of: [{GlobalPos, LocalPos, NewValue}]
%% Returns:
%%    [{GlobalPos, SortedUpdates}] 
%%       where
%%       SortedUpdates is a list of all field updates corresponding to the subtuple of GlobalPos,
%%       and in ascending order of their LocalPos.
%%       By subtuple, we mean the following: Object = {Class, SubTuple1, SubTuple2, ...}, where 
%%       SubTupleN = {LocalClassN, FieldN1, FieldN2, ...}
sort_updates(Updates2) ->
    Updates3 = misc:partition_list(1, Updates2),
    Updates4 = lists:map(fun ({GlobalPos, LocalItems}) ->
                            {GlobalPos,
                             lists:sort(fun ({_, LocalPos1, _}, {_, LocalPos2, _}) ->
                                            LocalPos1 < LocalPos2
                                        end,
                                        LocalItems)
                            }
                         end, Updates3),
    Updates4.

%% Function: resolve_updates(Updates, Definition, CxClass)
%% Purpose: given a list of AST form of the field updates, they are transofmred to a more usable form
%% Argumens:
%%   Updates: list of field updates, according to the AST of the original update-expression
%%   Definition: definition of the class used in the update
%%   CxClass: the class whose source code contains the update expression, or none if it is not a class
%% Returns:
%%   [{GlobalPos, LocalPos, ValExpr}] where each list item corresponds to the update of one field.
%%   They specify the position of the field in the tuple structure of the object, and the new value od the field.
%%   (The new value may be an expression, which have to be evaluated before assigning it to the field.)
%% Throws:
%%   {translateError, Line, Message} if non-existent or non-accessible fields are referred in updates
resolve_updates(Updates1, Definition, CxClass) ->
    Updates2 = lists:map(fun ({record_field, Line, {atom,_,Field}, ValExpr}) ->
                            case classes:fieldpos(Definition, Field, CxClass) of
                            {ok, GlobalPos, LocalPos} ->
                                {GlobalPos, LocalPos, ValExpr};
                            {error, Message} ->
                                throw({translateError, Line, Message})
                            end
                         end,
                         Updates1),
    Updates2.

%% Function: eval_update_exprs(State1, VarGenFun, Updates, Line)
%% Purpose: Evaluates the (possibly complex) value expressions of the updates into variables.
%% Arguments:
%%   State1, VarGenFun: used for generating variable names, see head comment of translate_update/7
%%   Updates: list of field updates as returned from sort_updates/1:  [{GlobalPos, LocalPos, NewValueExpr}]
%%   Line: line number of the generated AST nodes
%% Returns:
%%   {State2, Updates2, PreExprs}, where
%%   State2 is the last state of the variable name generator
%%   Updates2 is Updates, but with value expressions replaced with variables  [{GlobalPos, LocalPos, NewValueVar}]
%%   PreExprs is a list of variable assignments to initialise the new variables of Updates2 to the corresponding values
%%    in Updates1, e.g.: NewValueVar1 = NewValueExpr1, NewValueVar2 = NewValueExpr2, ...
eval_update_exprs(State1, VarGenFun, [Update0 = {GlobalPos, LocalPos, ValExpr}|Updates], Line) ->
    {State2, Updates1, PreExprs1} = eval_update_exprs(State1, VarGenFun, Updates, Line),
    ValExprType = element(1, ValExpr),
    if ValExprType =:= var
        orelse ValExprType =:= atom
        orelse ValExprType =:= integer
        orelse ValExprType =:= float
        orelse ValExprType =:= string
        orelse ValExprType =:= char
        orelse ValExprType =:= nil ->
            {State2, [Update0|Updates1], PreExprs1};
        true ->
            {State3, VarName} = VarGenFun(State2),
            {State3
            ,[{GlobalPos, LocalPos, ast:var(VarName, Line)}|Updates1]
            ,[ast:match(ast:var(VarName, Line), ValExpr, Line)|PreExprs1]
            }
    end;
eval_update_exprs(State1, _, [], _) ->
    {State1, [], []}.

%% Function: subobj_updates(State, VarGenFun, Updates, RootVar, Line)
%% Purpose:
%%    Create a setelement/3 chain for each subtuple of the object. The innermost element of the chain is an
%%    element/2 call on RootVar, that extracts each subtuple from the object.
%% Arguments:
%%   State1, VarGenFun: used for generating variable names, see head comment of translate_update/7
%%   Updates: list of all field updates, in the form returned from eval_update_exprs/4:
%%            [{GlobalPos, LocalPos, NewValueVariable}]
%%   RootVar: the variable refering the object to update
%%   Line: line number to insert into AST nodes
%% Returns:
%%   {State2, [{GlobalPos, {VarName, ChainExpr}}]}
%%      where each item is about updating the subtuple at position GlobalPos; 
%%      the updates subtuple is assigned to VarName, and this is done by the expression ChainExpr
subobj_updates(State1, VarGenFun, [{GlobalPos, SubUpdates1}|Updates], RootVar, Line) ->
    {State2, VarName} = VarGenFun(State1),
    SubUpdateChain = apply_subupdates(ast:element(GlobalPos, RootVar, Line), SubUpdates1, Line),
    
    {State3, Updates2} = subobj_updates(State2, VarGenFun, Updates, RootVar, Line),
    
    {State3,
        [{GlobalPos, {VarName, ast:match(ast:var(VarName, Line), SubUpdateChain, Line)}}
        |Updates2
        ]
    };
subobj_updates(State1, _, [], _, _) ->
    {State1, []}.
apply_subupdates(RootExpr, [{_, LocalPos, UpdateVar}|Updates], Line) ->
    ast:setelement(LocalPos, apply_subupdates(RootExpr, Updates, Line), UpdateVar, Line);
apply_subupdates(RootExpr, [], _) ->
    RootExpr.

%% Function: reconstruct_object(Updates, RootVar, Line)
%% Purpose: creates an AST expression, that constructs the updated instance of the object to update,
%%          from its already updates subtuples.
%% Arguments:
%%   Updates: update expression for each subtuple, as returned by apply_subupdates/3: [{GlobalPos, {ResultVar, UpdateExpr}}]
%%   RootVar: the original object that was updated
%%   Line: line number to insert into AST nodes
%% Returns:
%%   A setelement/3 chain, that given the results of subtuple updates, reconstructs the updated
%%   instance of the original object.
reconstruct_object([{GlobalPos, {VarName, _}}|Updates], RootVar, Line) ->
    ast:setelement(GlobalPos, reconstruct_object(Updates, RootVar, Line), ast:var(VarName, Line), Line);
    %reconstruct_object(Updates, RootVar, Line);
reconstruct_object([], RootVar, _) ->
    RootVar.